/**
 * Created by Administrator on 2015/12/7.
 */
/**
 * 此模板只是实现了三个功能
 * （1）显示值
 * <property value={value}></property>
 * （2）if语句
 * <if test={value}>xxx</if>
 * （3）循环语句
 * <iterator value={value}>xxxxx<iterator>
 *  展望要实现
 */

var FS = require('fs');
var str = '<iterator value="xxx">dfadafsdf</iterator>';
//var pattren = /<(\w+).*>.*<\/\1>/;
//console.log(pattren.test(str))
//replace3(str);
function getValue(name,options){
    if(!name)
    {
        return undefined;
    }
    var arr = name.split('.');
    options = options || {};
    if(arr.length > 1){
        var i = 0;
        var len = arr.length;
        var subObj = options;
        while(i < len && typeof subObj === 'object'){
            subObj = subObj[arr[i]];
            i = i + 1;
        }
        if(i === len){
            return subObj;
        }else {
            return undefined;
        }
    }else{
        return options[name];
    }
}
var keys ={
    "undefined":true,
    "null":true
}
function addThis(str){
    var pattern = /((["']?)([A-Za-z_]\w*)\2)/g;
    return str.replace(pattern,function (match,p1,p2,p3){
        if(p2 === '"' || p2 === "'"){
            return p1;
        }
        else{
            if(keys[p3]){
                return p3;
            }
            return "this."+p3;
        }
    })
}
/*console.log(getValue('p.address',
    {
        p:{
            address:{info:'xxx'},
            company:{
                address:{
                    info:'cnooc'
                }
            }
        }
    }));//{info:'xxx'}
console.log(getValue('p.company.address',
    {
        p:{
            address:{info:'xxx'},
            company:{
                address:{
                    info:'cnooc'
                }
            }
        }
    }))//{info:'cnooc'}
console.log(getValue('p.c',
    {
        p:{
            address:{info:'xxx'},
            company:{
                address:{
                    info:'cnooc'
                }
            }
        }
    }))//undefine*/
var tp = '<iterator value={names}>我是<property value={top}></property></iterator>'
var ops = {names:['dongge','zeus','yuzhou']};
var tp1 = '<property value="name"></property>';
var ops1 = {name:'dongge'};
var ifstr = '<if test={name === "dongge"}>If test</if>';
//console.log(replace3(tp1,ops1));
//console.log(replace3(tp,ops));
//console.log(replace3(ifstr,ops1));
var tp2 = '<iterator value={ints}><if test={top > 3}><property value={top}></property> is bigger than 3</if></iterator>';
var ops2 = {ints:[1,2,3,4,5]};
//console.log(replace3(tp2,ops2));
function replace3(template,options){
    //var pattern =/<(iterator|property|if)\s*([\w\.}"{=]+)>(.*)<\/\1>/g;if cannot work
    //var pattern =/<(iterator|property|if)\s+(.*)>(.*)<\/\1>/g;iterator cannot work
    //var pattern =/<(iterator|property|if)([^>]*)>(.*)<\/\1>/g;
    //var pattern =/<(iterator|property|if)\s*(\w+\s*=\s*\{[^}]*})>(.*)<\/\1>/g;
    var pattern =/<(iterator|property|if)\s*(\w+\s*=\s*\{[^}]*})>(.*?)<\/\1>/g;//采用非贪婪匹配
    return template.replace(pattern,function(match,p1,p2,p3,offset,string){
        if(p1 === 'iterator'){
            var pName = getProperty('value',p2);
            //var value = getValue(pName,options);
            var value = calcExp(options,pName);
            value = value || {};
            var retArr = [];
            for(var i in value){
                var loptions = value[i];
                if(typeof loptions!=='object'){
                    loptions = {top:value[i]};
                }else{
                    loptions.top = value[i];
                }
                var ret = replace3(p3,loptions);
                retArr.push(ret);
            }
            return retArr.join('');
        }
        if(p1 === 'property'){
            //var value = getValue(getProperty('value',p2),options);
            var value = calcExp(options,getProperty('value',p2));
            //console.log(value);
            return value||'';
        }
        /**
         * 这里的修改剔除了添加this的功能
         * 给变量添加this其实就是为了获取
         * options中的变量，这种方法有很多不合理之处，实际上还需要对整个表达式进行解析
         * 包括过滤掉关键字等问题
         * 这里使用eval(var...)
         * 来动态增加闭包中的变量，然后通过eval来计算
         * 表达式的值，这种方法比较合理，使用动态函数来计算表达式的值不好控制闭包问题
         */
        if(p1 === 'if'){
            var testStr = getProperty('test',p2);
            //testStr = addThis(testStr);
            //var testFun = new Function('return '+ testStr);

            //if(testFun.call(options) === true){
            if(calcExp(options,testStr)){
                return replace3(p3,options);
            }else{
                return '';
            }
        }
    })
}
/**
 * 如果出现异常将会返回undefined
 * options必须要为对象才行
 * @param options
 * @param exp
 * @returns
 */
function calcExp(options,exp){
    if(typeof  options ==='object'){
        for(var i in options){
            eval('var '+ i + '=' + JSON.stringify(options[i]));
        }
    }
    try{
        var ret = eval(exp);
    }catch (err){
        ret = undefined
    }
    return ret;
}
/**
 * 属性值的格式是name={value}
 * @param name
 * @param str
 * @returns value
 */
function getProperty(name,str){
    //var regex = new RegExp(''+ name+'={([\\w\\.]+)}');
    var regex = new RegExp(''+ name+'={\s*([^}]+)\s*}');//改动去掉value前后的空格
    var match = regex.exec(str);
    if(match === null){
        return undefined;
    }
    return match[1];
}
/*var name = 'value';
var propertyValue= 'value="user.name"';
var pstr = ''+ name+'="([\\w\\.]+)"';
console.log(pstr);
var regex = new RegExp(''+ name+'="([\\w\\.]+)"');
var match = regex.exec(propertyValue);
console.log(match);

var pattern = /value="([\w\.]+)"/;
console.log(pattern.exec(propertyValue))*/
function replace2(template,options){
    var pattern = /{([_\w\.]+)}/g;
    return template.replace(pattern,function(match,p1,offset,string){
        var value = getValue(p1,options);
        if(value === undefined){
            return '';
        }else{
            return value.toString();
        }
    })
}
/*var template = '{name} hello ,welcome to {country}';
console.log(replace2(template,{name:'dongge',country:'china'}));//dongge hello ,welcome to china
//console.log(replace2(template));hello , welcome to:good*/
function replace1(template,obj){
    var pattern = /{([_\w\.]+)}/g;
    return template.replace(pattern,function(match,p1,offset,string){
        var arr = p1.split('.');
        if(arr.length > 1){
            var i = 0;
            var len = arr.length;
            var subObj = obj[arr[0]];
            while(typeof subObj === 'object' && i < len){
                i = i + 1;
                subObj = subObj[arr[i]];
            }
            if(i === len-1){
                if(subObj === undefined){
                    return "";
                }else{
                    return subObj;
                }
            }else {
                return "";
            }
        }else{
            var ret = obj[p1]
            if(ret === undefined)
            {
                return "";
            }else{
                return ret;
            }
        }

    })
}

var tp4 = '<article id="<property value={_id}></property>" class="post tag-about-ghost tag-release">'+
    '<div class="post-head">'+
    '<h1 class="post-title"><a href="/article/detail?id=<property value={_id}></property>"><property value={title}></property></a></h1>'+
    '<div class="post-meta">'+
    '<span class="author">作者：<a href="/article/list?userId={user._id}"><property value={user.username}></property></a></span> •'+
    '<time><property value={date}></property></time></div></div>'+
    '<div class="post-content"><p><property value={body}></property></p></div>'+
    '<footer class="post-footer clearfix">'+
    '<div class="pull-left tag-list">'+
    '<i class="fa fa-folder-open-o"></i>'+
    '<a href="#">Ghost</a>, <a href="#">新版本发布</a></div>'+
    '<div class="pull-right share"></div></footer></article>';
/*console.log(replace3(tp4,{_id:'123465465',
        title:'This is a test',
        body:'asdfasdfasdfasdfas'
    }));*/

+function () {
    var tp5 = "<include value={1.txt}></include>This is a test<include value={2.txt}></include>";
    var pattern =/<include\s*(\w+\s*=\s*\{([^}]*)})>(.*?)<\/include>/g;
    var match = null;
    var arr = [];
    var lastIndex = 0;
    while(match = pattern.exec(tp5)){
        if(lastIndex === match.index){
            arr.push(readFile(match[2]));
        }else {
            arr.push(tp5.slice(lastIndex,match.index));
            arr.push(readFile(match[2]));
        }
        lastIndex = pattern.lastIndex;
        console.log(match);

    }
    //console.log(arr);
    function readFile(name){
        return function(callback){
            FS.readFile(name,callback);
        }
    }
    function f(i){
        if(i === arr.length){
            console.log(arr);
            return;
        }
        var fun = arr[i];
        if(typeof  fun == 'function'){
            fun(function(err,data){
                if(err){
                    arr[i] = '';
                }else {
                    arr[i] = data.toString();
                }
                f(i+1);
            });
        }else {
            f(i+1);
        }
    }
    f(0);
}();

function handleInclude(template,options,callback){
    var pattern =/<include\s*(\w+\s*=\s*\{([^}]*)})>(.*?)<\/include>/g;
    var match = null;
    var arr = [];
    var lastIndex = 0;
    while(match = pattern.exec(template)){
        if(lastIndex === match.index){
            //arr.push(readFile(calcExp(options,JSON.stringify(match[2]))));//calcExp(options,pName);
            arr.push(readFile(calcExp(options,match[2])));
        }else {
            arr.push(template.slice(lastIndex,match.index));
            //arr.push(readFile(calcExp(options,JSON.stringify(match[2]))));
            arr.push(readFile(calcExp(options,match[2])));
        }
        lastIndex = pattern.lastIndex;
        //console.log(match);
    }
    //console.log(lastIndex);
    //console.log(pattern.lastIndex);
    //console.log(template.length);
    if(lastIndex != template.length){
        arr.push(template.slice(lastIndex,template.length));
    }
    function readFile(name){
        return function(callback){
            FS.readFile(name,callback);
        }
    }
    function f(i){
        if(i === arr.length){
            if(callback){
                callback(arr.join(''));
            }
            return;
        }
        var fun = arr[i];
        if(typeof  fun == 'function'){
            fun(function(err,data){
                if(err){
                    arr[i] = '';
                }else {
                    arr[i] = data.toString();
                }
                f(i+1);
            });
        }else {
            f(i+1);
        }
    }
    //if(arr.length == 0){//如果template中没有include指令
    //    callback(template);
    //}else {
    //    f(0);
    //}
    f(0);
}

+function(){
   console.log('**************debug include****************************');
    var tp6 = "<include value={'1.txt'}></include>This is a test<include value={'2.txt'}></include>";
   //handleInclude(tp6,{},console.log);
   console.log('**************debug include end************************');
}();
function replace4(template,options){
    //var pattern =/<(iterator|property|if)\s*([\w\.}"{=]+)>(.*)<\/\1>/g;if cannot work
    //var pattern =/<(iterator|property|if)\s+(.*)>(.*)<\/\1>/g;iterator cannot work
    //var pattern =/<(iterator|property|if)([^>]*)>(.*)<\/\1>/g;
    //var pattern =/<(iterator|property|if)\s*(\w+\s*=\s*\{[^}]*})>(.*)<\/\1>/g;
    var pattern =/<(iterator|property|if)\s*(\w+\s*=\s*\{[^}]*})>([^]*?)<\/\1>/g;//采用非贪婪匹配
    /**
     * .不能匹配\r\n
     * 使用[^]可以匹配所有的字符包括回车换行符号
     * [\s\S]也是可以的
     */
    return template.replace(pattern,function(match,p1,p2,p3,offset,string){
        if(p1 === 'iterator'){
            var pName = getProperty('value',p2);
            //var value = getValue(pName,options);
            var value = calcExp(options,pName);
            value = value || {};
            var retArr = [];
            for(var i in value){
                var loptions = {};
                var v = value[i];
                loptions.top = v;
                if(typeof  v === 'object'){
                    Object.keys(v).forEach(function(prop){
                        loptions[prop] = v[prop];
                    })
                }
                /**
                 * 迭代之后必须更新option给下面的模板
                 * 这里额外添加一个top属性，代表本对象
                 * 如果迭代值也是个对象就把其中的属性复制到option中
                 * 存在问题：
                 * 如果在循环中要获取不在迭代中的值，就无法获取
                 * 要能沿着作用域向上搜索
                 */
                var ret = replace4(p3,loptions);
                retArr.push(ret);
            }
            return retArr.join('');
        }
        if(p1 === 'property'){
            //var value = getValue(getProperty('value',p2),options);
            var value = calcExp(options,getProperty('value',p2));
            //console.log(value);
            if(value === null || value === undefined){
                return '';
            }else{
                return value;
            }
            //return value||''; 如果value=0，那么value||''等于空，这显然不对 2016.1.16
        }
        /**
         * 这里的修改剔除了添加this的功能
         * 给变量添加this其实就是为了获取
         * options中的变量，这种方法有很多不合理之处，实际上还需要对整个表达式进行解析
         * 包括过滤掉关键字等问题
         * 这里使用eval(var...)
         * 来动态增加闭包中的变量，然后通过eval来计算
         * 表达式的值，这种方法比较合理，使用动态函数来计算表达式的值不好控制闭包问题
         */
        if(p1 === 'if'){
            var testStr = getProperty('test',p2);
            //testStr = addThis(testStr);
            //var testFun = new Function('return '+ testStr);

            //if(testFun.call(options) === true){
            if(calcExp(options,testStr)){
                return replace4(p3,options);
            }else{
                return '';
            }
        }
    })
}
/**
 * 第五版的replace增加include指令,include指令要读取文件（异步调用）
 * 先处理include指令，后面再处理property iterator if
 * @param template
 * @param options
 * @param callback
 * @returns {*}
 */
function replace5(template,options,callback){
    handleInclude(template,options,function(result){
        var ret = replace4(result,options);
        if(callback){
            callback(ret);
        }
    });
}

+function(){//测试replace5
    var tp7 = "<include value={'1.txt'}></include>This is a test<include value={'2.txt'}></include>";
    var tp8 = "<property value={name}></property>";
    var tp9 = "<include value={'1.txt'}></include>This is a test";
    var tp10 = "<iterator value={articles}><property value={title}></property></iterator>";

    //1.txt:<property value={name}></property>
    //2.txt:2.txt
    var options = {
        articles:[{title:'--1--'},{title:'--2--'}]
    };
    /*replace5(tp7,{},console.log);//This is a test2.txt
    replace5(tp8,{name:'dongge'},console.log);//dongge
    replace5(tp9,{},console.log);//This is a test*/
    //replace5(tp10,options,console.log);
}();
function render2(filePath,options,callback){
    FS.readFile(filePath, function (err, content) {
        if (err) return callback(new Error(err));
        replace5(content.toString(),options,function(ret){
            callback(null,ret);
        });
    })
}
+function(){
    var tp11 =
        '<iterator value={articles}>\r\n'+
        '<article>'+
        '<div class="header">'+
        '<h3><property value={title}></property></h3>'+
        '<p><span>作者：<a href="/article/">东哥</a></span>&nbsp;<span><property value={date}></property></span></p>'+
        '</div>'+
        '<div class="content">'+
        '<p><property value={content}></property></p>'+
        '<a href="/article/get?id=<property value={_id}></property>">更多</a>'+
        '</div>'+
        '<div class="keys">'+
        '<span><property value={keys}></property></span>'+
        '</div>'+
        '</article>'+
        '</iterator>';
    var msg = {
        articles:[{
            _id:'1534313213213',
            title:'This is a test',
            content:'This is a test also',
            date:'2016/1/24',
            keys:'jquery,match'
        }]
    };
    replace5(tp11,msg,console.log);
    //render2('list.html',msg,console.log);
    //文件中的/r/n影响到正则表达式的匹配
}();